package com.bzchao.poiall.service;

import org.docx4j.convert.in.xhtml.XHTMLImporterImpl;
import org.docx4j.fonts.IdentityPlusMapper;
import org.docx4j.fonts.Mapper;
import org.docx4j.fonts.PhysicalFont;
import org.docx4j.fonts.PhysicalFonts;
import org.docx4j.jaxb.Context;
import org.docx4j.openpackaging.exceptions.InvalidFormatException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.DocumentSettingsPart;
import org.docx4j.openpackaging.parts.WordprocessingML.FooterPart;
import org.docx4j.openpackaging.parts.WordprocessingML.HeaderPart;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.relationships.Relationship;
import org.docx4j.wml.*;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;

public class Html2DocxService {

    // Factory for creating WML Object.
    private static final ObjectFactory factory = Context.getWmlObjectFactory();

    /**
     * Convert HTML to DOCX(OPENXML).
     *
     * @param html the source, a HTML file
     * @param docx the target, a DOCX file
     */
    public void convert(InputStream html, OutputStream docx) {
        try {
            // Creates a WordprocessingMLPackage, using default page size and orientation,
            // default to A4 portrait.
            WordprocessingMLPackage pack = WordprocessingMLPackage.createPackage();
            configSimSunFont(pack);
            // Convert XHTML + CSS to WordML content.
            // XHTML must be well formed XML.
            XHTMLImporterImpl importer = new XHTMLImporterImpl(pack);
            // Convert the well formed XHTML contained in file to a list of WML objects.
            List<Object> list = importer.convert(html, null);
            MainDocumentPart mainPart = pack.getMainDocumentPart();
            // Add all WML objects to MainDocumentPart.
            mainPart.getContent().addAll(list);
            setFooter(pack, mainPart);
            setHeader(pack, mainPart);
            // set compatibilityMode to 15
            // to avoid Word 365/2016 saying "Compatibility Mode"
            DocumentSettingsPart settingsPart = mainPart.getDocumentSettingsPart(true);
            CTCompat compat = factory.createCTCompat();
            compat.setCompatSetting("compatibilityMode", "http://schemas.microsoft.com/office/word", "15");
            settingsPart.getContents().setCompat(compat);
            // save to a file
            pack.save(docx);
        } catch (Exception e) {
            throw new HTML2DocxException("covert html to docx error.", e);
        }
    }

    private void setHeader(WordprocessingMLPackage pack, MainDocumentPart mainPart) throws InvalidFormatException {
        // create header
        Hdr header = createHeader("移动展业");
        HeaderPart headerPart = new HeaderPart();
        headerPart.setPackage(pack);
        headerPart.setJaxbElement(header);
        Relationship headerRelation = mainPart.addTargetPart(headerPart);
        HeaderReference headerRef = factory.createHeaderReference();
        headerRef.setId(headerRelation.getId());
        headerRef.setType(HdrFtrRef.DEFAULT);
        SectPr sectPr = pack.getDocumentModel().getSections().get(0).getSectPr();
        sectPr.getEGHdrFtrReferences().add(headerRef);
    }

    private void setFooter(WordprocessingMLPackage pack, MainDocumentPart mainPart) throws InvalidFormatException {
        // create footer
        Ftr footer = createFooterWithPageNumber();
        FooterPart footerPart = new FooterPart();
        footerPart.setPackage(pack);
        footerPart.setJaxbElement(footer);
        Relationship footerRelation = mainPart.addTargetPart(footerPart);
        FooterReference footerRef = factory.createFooterReference();
        footerRef.setId(footerRelation.getId());
        footerRef.setType(HdrFtrRef.DEFAULT);
        SectPr sectPr = pack.getDocumentModel().getSections().get(0).getSectPr();
        sectPr.getEGHdrFtrReferences().add(footerRef);
    }

    // create a header
    private Hdr createHeader(String content) {
        Hdr header = factory.createHdr();
        P paragraph = factory.createP();
        PPr ppr = factory.createPPr();
        Jc jc = factory.createJc();
        jc.setVal(JcEnumeration.CENTER);
        ppr.setJc(jc);
        paragraph.setPPr(ppr);
        R run = factory.createR();
        Text text = new Text();
        text.setValue(content);
        run.getContent().add(text);
        paragraph.getContent().add(run);
        header.getContent().add(paragraph);
        return header;
    }

    // create a header with page number
    private Ftr createFooterWithPageNumber() {
        Ftr ftr = factory.createFtr();
        P paragraph = factory.createP();
        PPr ppr = factory.createPPr();
        Jc jc = factory.createJc();
        jc.setVal(JcEnumeration.RIGHT);
        ppr.setJc(jc);
        paragraph.setPPr(ppr);
        addFieldBegin(paragraph);
        addPageNumberField(paragraph);
        addFieldEnd(paragraph);
        ftr.getContent().add(paragraph);
        return ftr;
    }

    private void addPageNumberField(P paragraph) {
        Text txt = new Text();
        txt.setSpace("preserve");
        txt.setValue(" PAGE   \\* ArabicDash ");
        R run = factory.createR();
        run.getContent().add(factory.createRInstrText(txt));
        paragraph.getContent().add(run);
    }

    private void addFieldBegin(P paragraph) {
        FldChar fldChar = factory.createFldChar();
        fldChar.setFldCharType(STFldCharType.BEGIN);
        R run = factory.createR();
        run.getContent().add(fldChar);
        paragraph.getContent().add(run);
    }

    private void addFieldEnd(P paragraph) {
        FldChar fldChar = factory.createFldChar();
        fldChar.setFldCharType(STFldCharType.END);
        R run = factory.createR();
        run.getContent().add(fldChar);
        paragraph.getContent().add(run);
    }


    /**
     * 为 {@link org.docx4j.openpackaging.packages.WordprocessingMLPackage} 配置中文字体
     *
     * @param wordMLPackage
     * @throws Exception
     */
    public void configSimSunFont(WordprocessingMLPackage wordMLPackage) throws Exception {
        Mapper fontMapper = new IdentityPlusMapper();
        String fontFamily = "SimSun";

        Map<String, PhysicalFont> physicalFonts = PhysicalFonts.getPhysicalFonts();
        fontMapper.put("SimSun", PhysicalFonts.get("SimSun"));

        fontMapper.put("隶书", PhysicalFonts.get("LiSu"));
        fontMapper.put("宋体", PhysicalFonts.get("SimSun"));
        fontMapper.put("微软雅黑", PhysicalFonts.get("Microsoft Yahei"));
        fontMapper.put("黑体", PhysicalFonts.get("SimHei"));
        fontMapper.put("楷体", PhysicalFonts.get("KaiTi"));
        fontMapper.put("新宋体", PhysicalFonts.get("NSimSun"));
        fontMapper.put("华文行楷", PhysicalFonts.get("STXingkai"));
        fontMapper.put("华文仿宋", PhysicalFonts.get("STFangsong"));
        fontMapper.put("宋体扩展", PhysicalFonts.get("simsun-extB"));
        fontMapper.put("仿宋", PhysicalFonts.get("FangSong"));
        fontMapper.put("仿宋_GB2312", PhysicalFonts.get("FangSong_GB2312"));
        fontMapper.put("幼圆", PhysicalFonts.get("YouYuan"));
        fontMapper.put("华文宋体", PhysicalFonts.get("STSong"));
        fontMapper.put("华文中宋", PhysicalFonts.get("STZhongsong"));
        wordMLPackage.setFontMapper(fontMapper);

        RFonts rfonts = Context.getWmlObjectFactory().createRFonts(); //设置文件默认字体
        rfonts.setAsciiTheme(null);
        rfonts.setAscii(fontFamily);
        wordMLPackage.getMainDocumentPart().getPropertyResolver()
                .getDocumentDefaultRPr().setRFonts(rfonts);
    }

    public class HTML2DocxException extends RuntimeException {

        private static final long serialVersionUID = -436242367279880231L;

        public HTML2DocxException() {
            super();
        }

        public HTML2DocxException(String message, Throwable cause, boolean enableSuppression,
                                  boolean writableStackTrace) {
            super(message, cause, enableSuppression, writableStackTrace);
        }

        public HTML2DocxException(String message, Throwable cause) {
            super(message, cause);
        }

        public HTML2DocxException(String message) {
            super(message);
        }

        public HTML2DocxException(Throwable cause) {
            super(cause);
        }

    }

}
